---
description: Yoga has experimental support for canceling the GraphQL execution.
---

# Execution Cancellation

In the real world, a lot of HTTP requests are dropped or canceled. This can happen due to a flakey
internet connection, navigation to a new view or page within a web or native app or the user simply
closing the app. In this case, the server can stop processing the request and save resources.

That is why Yoga comes with experimental support for canceling the GraphQL execution upon request
cancellation.

## Getting started

To enable execution cancellation, you need to add the `useExecutionCancellation` plugin to your Yoga
instance.

```ts filename="Execution Cancellation configuration" {1,6}
import { createYoga, useExecutionCancellation } from 'graphql-yoga'
import { schema } from './schema'

// Provide your schema
const yoga = createYoga({
  plugins: [useExecutionCancellation()],
  schema
})

// Start the server and explore http://localhost:4000/graphql
const server = createServer(yoga)
server.listen(4000, () => {
  console.info('Server is running on http://localhost:4000/graphql')
})
```

That is all you need to do to enable execution cancellation in your Yoga server. Theoretically, you
can enable this and immediately benefit from it without making any other adjustments within your
GraphQL schema implementation.

If you want to understand how it works and how you can adjust your resolvers to properly cancel
pending promises (e.g. database reads or HTTP requests), you can continue with the next section.

## How it works

The following example demonstrates how the execution cancellation works.

```graphql filename="Simple GraphQL schema"
type Query {
  user: User
}

type User {
  id: ID!
  name: String!
  bestFriend: User
}
```

The `Query.user` resolver has a artificial delay of 10 seconds to simulate a long-running operation
e.g. a slow read from a database.

```ts filename="Full Yoga Example"
import { createServer } from 'node:http'
import { setTimeout as setTimeout$ } from 'node:timers/promises'
import { createLogger, createSchema, createYoga, useExecutionCancellation } from 'graphql-yoga'

const logger = createLogger('debug')

const schema = createSchema({
  typeDefs: /* GraphQL */ `
    type Query {
      user: User
    }

    type User {
      id: ID!
      name: String!
      bestFriend: User
    }
  `,
  resolvers: {
    Query: {
      async user(_, __, { request }) {
        logger.info('resolving user')

        await setTimeout$(5000)

        logger.info('resolved user')

        return {
          id: '1',
          name: 'Chewie'
        }
      }
    },
    User: {
      bestFriend() {
        logger.info('resolving user best friend')

        return {
          id: '2',
          name: 'Han Solo'
        }
      }
    }
  }
})

const yoga = createYoga({
  plugins: [useExecutionCancellation()],
  schema,
  logging: logger
})

const server = createServer(yoga)
server.listen(4000, () => {
  console.info('Server is running on http://localhost:4000/graphql')
})
```

With this server setup we are going to execute the following GraphQL query.

```graphql filename="GraphQL Query for fetching user with best friend"
query UserWithBestFriend {
  user {
    id
    name
    bestFriend {
      id
      name
    }
  }
}
```

For your convenience, you can copy the following `curl` command to execute the query.

```bash filename="Curl Request for executing the operation"
curl -g \
  -X POST \
  -H "content-type: application/json" \
  -d '{"query":"{ user { id name bestFriend { id name } } }"}' \
  "http://localhost:4000/graphql"
```

Execute this will give us the following server log output:

```bash filename="Server logs"
DEBUG Parsing request to extract GraphQL parameters
DEBUG Processing GraphQL Parameters
INFO resolving user
INFO resolved user
INFO resolving user best friend
DEBUG Processing GraphQL Parameters done.
```

Now, let's cancel the request by closing the terminal or pressing `Ctrl + C` after the server shows
the `INFO resolving user` log.

This will now log the following.

```bash filename="Server logs for canceled request"
DEBUG Parsing request to extract GraphQL parameters
DEBUG Processing GraphQL Parameters
INFO resolving user
DEBUG Request aborted
INFO resolved user
```

The interesting part is that now `DEBUG Request aborted` is shown. At this point, any further
GraphQL execution will be stopped and no other GraphQL resolvers will be invoked.

However, the `INFO resolved user` log is still showing up.

We can further adjust our server to cancel the artificial delay promise when the request is aborted
to avoid any further processing.

```ts filename="Altered server example with timer cleanup" {22-28}
import { createServer } from 'node:http'
import { createLogger, createSchema, createYoga, useExecutionCancellation } from 'graphql-yoga'

const logger = createLogger('debug')

const schema = createSchema({
  typeDefs: /* GraphQL */ `
    type Query {
      user: User
    }

    type User {
      id: ID!
      name: String!
      bestFriend: User
    }
  `,
  resolvers: {
    Query: {
      async user(_, __, { request }) {
        logger.info('resolving user')
        await new Promise((resolve, reject) => {
          const timeout = setTimeout(resolve, 5000)
          request.signal.addEventListener('abort', () => {
            clearTimeout(timeout)
            reject(request.signal.reason)
          })
        })
        logger.info('resolved user')

        return {
          id: '1',
          name: 'Chewie'
        }
      }
    },
    User: {
      bestFriend() {
        logger.info('resolving user best friend')

        return {
          id: '2',
          name: 'Han Solo'
        }
      }
    }
  }
})

// Provide your schema
const yoga = createYoga({
  plugins: [useExecutionCancellation()],
  schema,
  logging: logger
})

// Start the server and explore http://localhost:4000/graphql
const server = createServer(yoga)
server.listen(4000, () => {
  console.info('Server is running on http://localhost:4000/graphql')
})
```

After these adjustments, the timer will be cleaned up properly, and `INFO resolved user`, will no
longer show up in the logs.

```bash filename="Server Logs after timer cleanup"
DEBUG Parsing request to extract GraphQL parameters
DEBUG Processing GraphQL Parameters
INFO resolving user
DEBUG Request aborted
```

## Propagate cancellation in resolvers

As shown in the previous Selection, you can utilize the Yoga context object `request.signal` to
cancel pending asynchronous operations in your resolvers.

### `fetch` example

In this example we just pass `context.request.signal` to the fetch call. This will cancel the fetch
request when the GraphQL request is canceled.

```ts filename="Resolver fetch cancellation example" {15}
import { createSchema, createYoga } from 'graphql-yoga'

const yoga = createYoga({
  schema: createSchema({
    typeDefs: /* GraphQL */ `
      type Query {
        greeting: String!
      }
    `,
    resolvers: {
      Query: {
        greeting: async (_, args, context) => {
          // This service does not exist
          const greeting = await fetch('http://localhost:9876/greeting', {
            signal: context.request.signal
          }).then(res => res.text())

          return greeting
        }
      }
    }
  })
})

const server = createServer(yoga)
server.listen(4000, () => {
  console.info('Server is running on http://localhost:4000/graphql')
})
```

## Further Resources

The execution cancelation API is built on top of the `AbortController` and `AbortSignal` APIs.

- [MDN AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) and
- [MDN AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) APIs

The execution cancelation is a feature of our GraphQL executor that is a fork of `graphql-js`.

- [`@graphql-tools/executor`](https://github.com/ardatan/graphql-tools/tree/master/packages/executor)
- [Yoga request cancelation example](https://github.com/graphql-hive/graphql-yoga/tree/main/examples/request-cancelation)
